/**\file gu_shader_adapter.cpp */

#include "gu_shader_parser.h"


#include<regex>
#include<iostream>
#include<sstream>
#include<tuple>
#include<algorithm>
#include<iterator>
#include<map>
#include<cassert>
#include<list>

namespace gu
{

typedef std::map<TypeMapping, std::string> MapNameDictionary;
typedef std::map<VariableType, std::string> TypeNameDictionary;

template<int DIALECT> MapNameDictionary GetMapNames();
template<int DIALECT> TypeNameDictionary GetTypeNames();

template<> static MapNameDictionary GetMapNames<GLSL_330>()
{
    MapNameDictionary map;
    map[gu::IN]      = std::string("in");
    map[gu::OUT]     = std::string("out");
    map[gu::UNIFORM] = std::string("uniform");
    return map;
}

template<> static TypeNameDictionary GetTypeNames<GLSL_330>()
{
    TypeNameDictionary map;
    map[SCALAR]    = std::string("float");
    map[VEC3]      = std::string("vec3");
    map[VEC4]      = std::string("vec4");
    map[MAT4]      = std::string("mat4");
    map[TEXTURE2D] = std::string("sampler2D");
    return map;
}

#if 0
/** Search string for the string mapping of specific keytype . Return KEY if strings match.*/
template<class KEY>
bool keyFromString(const std::string& string,  const std::map<KEY, std::string>& map, KEY& keyType) const
{
    bool result = false;
    auto end = map.end();

    auto match = [&](decltype(map.begin()) pair)->bool{
        return string.find(pair->second) != std::string::npos;
    };

    auto pair = std::find_if(map.begin(), map.end(), match);

    if(pair != end)
    {
        result = true;
        keyType = pair->first;
    }

    return result;
}
#endif

#if 0
/** Search string for the string mapping of specific keytype . Return KEY if strings match.*/
template<class KEY>
bool keyFromString(const std::string& string,  const std::map<KEY, std::string>& map, KEY& keyType)
{
    bool result = false;

    auto match = [&](decltype(map.begin()) pair)->bool{
        return string.find(pair->second) != std::string::npos;
    };

    if(auto pair = gr::tryGetPair(map, match))
    {
        result = true;
        keyType = pair->first;
    }

    return result;
}
#endif

template<int DIALECT>
class VariableParseMap
{
    public:
        const MapNameDictionary  mapNames;
        const TypeNameDictionary typeNames;

    VariableParseMap():mapNames(GetMapNames<DIALECT>()), typeNames(GetTypeNames<DIALECT>())
    {}

    std::string toString(const Variable& var)
    {
        std::ostringstream out;
        out << mapNames[var.mapping] << " " << typeNames[var.type] << " " << var.name;
        return out.str();
    }

    bool fromString(const char* str, TypeMapping& map, VariableType& type)
    {
        bool result = false;
        if(mapNames.typeFromString(str, map) && typeNames.typeFromString(str, type))
        {
            result = true;
        }
        return result;
    }
};


struct TextLine
{
    std::string string;
    int         lineNumber;

    const char* begin() const {return string.c_str();}
    const char* end() const {return string.c_str() + string.size();}
    int length(){return string.size();}
};

TextLine makeTextLine(const char* buffer, int begin, int end, int line)
{
    TextLine t = {std::string(buffer + begin, buffer + end), line};
    return t;
}

std::list<TextLine> stringSplit(const char* str, const char* delim)
{
    std::list<TextLine> linesOut;
    const char* buffer     = str;
    int         delimSize  = strlen(delim);
    int         bufferSize = strlen(str);

    auto        addLine = [&] (const char* buffer, int start, int end, int& line)
    {
        linesOut.push_back(makeTextLine(buffer, start, end, line));
        line++;
    };
    
    int       lineStart = 0;  
    int       lines = 0;
    int       i = 0;
    while(i < bufferSize)
    {
        if(strncmp(buffer + i, delim, delimSize) == 0)
        {
            addLine(buffer, lineStart, i , lines);
            i += delimSize;
            lineStart = i;
        }
        else
        {
            i++;
        }
    }
    
    if(lineStart < bufferSize)
    {
        addLine(buffer, lineStart, i , lines);
    }
    return linesOut;
}

// return regex that will find a string matching one of the mapped names (A|B|..|C)
template<class K>
std::string composeMatchName(std::map<K,std::string>& map)
{
    std::ostringstream stream;

    if(map.size() > 0)
    {
        stream << "(";
        auto last = std::prev(map.end());
        std::for_each(map.begin(), last, [&](const std::pair<K, std::string>& pair){stream << pair.second << "|";});
        stream << last->second << ")";
    }

    return stream.str();
}

template<class K>
bool keyFromString(std::map<K, std::string>& map, const std::string& string,  K& keyType)
{
    auto match = [&](decltype(map.begin()) ipair)->bool{
        return string.find(ipair->second) != std::string::npos;
    };
    auto pair = gr::tryGetPair(map, match);
    if(isValid(pair))
        keyType = pair->first;

    return isValid(pair);
}

/** Compose regex that matches with <mapping> <type> <name>
 * */
template<int DIALECT>
std::regex composeShaderVariableRegex(VariableParseMap<DIALECT>& varmap)
{
    std::ostringstream stream;

    auto mapNames = varmap.mapNames;
    auto typeNames = varmap.typeNames;

    stream << composeMatchName(mapNames) << "\\s+" << composeMatchName(typeNames) << "\\s+(\\w+)";

    std::cout << "Composed regex:" << stream.str() << std::endl;

    return std::regex(stream.str());
}

template<int DIALECT>
void getVariablesFromLine(VariableParseMap<DIALECT>& varmap, const TextLine& line, std::regex& regex, std::list<Variable>& variables)
{
   // regex - use VariableMap<GLSL_330>::mapNames and VariableMap<GLSL_220>::typeNames
   // to create the regex

    std::cmatch match;
    if(std::regex_search(line.begin(), line.end(), match, regex))
    {
        TypeMapping map;
        VariableType type;

        auto mapNames = varmap.mapNames;
        auto typeNames = varmap.typeNames;

        if(keyFromString(mapNames, match[1], map) && keyFromString(typeNames, match[2], type))
        {
            std::string name(match[3]);
            Variable var = makeVariable(map, type, name.c_str());
            variables.push_back(var); 
        }
    }
}

template<int DIALECT>
void parseToVariables(const char* shader, std::list<Variable>& variables)
{
    VariableParseMap<DIALECT> varmap;
    std::regex regex = composeShaderVariableRegex(varmap);

    auto lines = stringSplit(shader, "\n");

    auto getVars = [&](const TextLine& line){getVariablesFromLine<DIALECT>(varmap, line, regex, variables);};
    std::for_each(lines.begin(), lines.end(), getVars);
}

std::list<Variable> parseToVariables(ShaderDialect dialect, const char* shader)
{
    std::list<Variable> variables;
    switch(dialect)
    {
        case GLSL_330: parseToVariables<GLSL_330>(shader, variables); break;
        default:
            assert(!"Unkonwn dialect");
    }
    return variables;
}

std::map<std::string, Variable> parseToVariableMap(ShaderDialect dialect, const char* shader)
{
    auto vars = parseToVariables(dialect, shader);
    std::map<std::string, Variable> map;

    std::for_each(vars.begin(), vars.end(), [&](Variable& v){map[v.name] = v;});
    return map;
}

}
